Link to this headingMag Stripe

  • Have three Tracks

Each track starts with byte that is used to figure out the start of the message.
Each track ends with byte that is used to figure out the end of the message.

Visual Graphic of Tracks

Link to this headingTrack 1

  • The start byte is 0x45 or “%”
  • The track has 7 bits to each byte. This allows characters 0x20 to 0x9F.
    • Each byte has a parity bit must have a odd number of bits.
      • If this is not so then the data is corrupted
      • The parity bit is the high bit of the data
    • Each bit is added to 0x20 which gets its range of characters

Link to this headingTrack 2

  • The start byte is 0x0b
  • The track has 5 bits to each byte. This allows characters 0x30 to 0x50.
    • Each bit is added to 0x30 which gets its range of characters

Link to this headingTrack 3

  • The start byte is 0x0b

Link to this headingReading with an Arduino

// Visual feedback when the card is being read... #define READ_LED 12 #define ERROR_LED 11 #define MAGSTRIPE_RDT 2 /* (RDT) data pin (blue) */ #define MAGSTRIPE_RCL 3 /* (RCL) clock pin (green) */ #define MAGSTRIPE_CLS 4 /* (CLS) card present pin (yellow) */ #define TRACK_NUM 2 /* * Track 3 is the one that can contain the most characters (107). * We add one more to accommodate the final '\0', as the data is a C string... */ static const byte DATA_BUFFER_LEN = 108; static char data[DATA_BUFFER_LEN]; // Variables used by the interrupt handlers... static volatile bool next_bit = 0; // next bit to read static volatile unsigned char bits[DATA_BUFFER_LEN]; // buffer for bits being read static volatile short num_bits = 0; // number of bits already read void data_callback(){ next_bit = !next_bit; } void clock_callback(){ // Avoid a crash in case there are too many bits (garbage)... if (num_bits >= (DATA_BUFFER_LEN * 8 )) { return; } //Get Character and Bit Index int char_index = num_bits / 8; int bit_index = num_bits % 8; //Add the Bit to the buffer bits[char_index] |= (next_bit << bit_index); //Increase the bit number num_bits++; } void setup() { //Setup Read and Error LEDs pinMode(READ_LED, OUTPUT); pinMode(ERROR_LED, OUTPUT); //Set LEDs OFF digitalWrite(READ_LED, LOW); digitalWrite(ERROR_LED, LOW); //Setup Serial to Computer Serial.begin(9600); //Setup Input from Mag Stripe pinMode(MAGSTRIPE_RDT, INPUT); pinMode(MAGSTRIPE_RCL, INPUT); pinMode(MAGSTRIPE_CLS, INPUT); //Setup Interrupts //Reading is more reliable when using interrupts... attachInterrupt(digitalPinToInterrupt(MAGSTRIPE_RDT), data_callback, CHANGE); // data pin attachInterrupt(digitalPinToInterrupt(MAGSTRIPE_RCL), clock_callback, FALLING); // clock pin } short find_start_byte(unsigned char pattern){ unsigned char bit_accum = 0; unsigned char bit_length = (TRACK_NUM == 1 ? 7 : 5); unsigned char new_bit; for (short i = 0; i < num_bits; i++) { bit_accum >>= 1; bit_accum |= bitRead(bits[i / 8], (i % 8)) << (bit_length - 1); // ...and add the current bit // Stop when the start sentinel pattern is found... if (bit_accum == pattern) { return i - (bit_length - 1); } } // No start sentinel was found... return -1; } short decode_magdata(char *data, unsigned char size){ unsigned char start_byte = (TRACK_NUM == 1 ? 0x45 : 0x0b); unsigned char chars = 0; unsigned char bit_length = (TRACK_NUM == 1 ? 7 : 5); //Find the sentinel that denotes the start of the magstripe data short start_bit_index = find_start_byte(start_byte); Serial.print("Start Byte: "); Serial.println(start_bit_index); if(start_bit_index < 0){ return -2; } //Convert Bits to data for (short bit_count = start_bit_index; bit_count < num_bits; bit_count += bit_length) { unsigned char bit_accum = 0; //Read the first bit_length bytes for (short j = 0; j < bit_length; j++) { short idx = bit_count + j; bit_accum |= (bitRead(bits[idx / 8], (idx % 8)) << j); } //Check if too long for buffer if (chars >= size) { return -3; } // Check if reached the end of the data if (bit_accum == 0) { break; } //Do Parity Check unsigned char parity = 0; for (unsigned char j = 0; j < 8; j++) { parity += (bit_accum >> j) & 1; } // The parity must be odd... if(parity % 2 == 0){ return -4; } //Remove the Parity bit from the bit_accum bit_accum &= ~(1 << (bit_length - 1)); // Convert the character to ASCII... data[chars] = (TRACK_NUM == 1 ? bit_accum + 0x20 : bit_accum + 0x30); Serial.print(data[chars]); chars++; } return chars; } void reset_data(){ //Reset Variables num_bits = 0; next_bit = 0; //Reset Buffers for(short i = 0; i<DATA_BUFFER_LEN; i++ ){ data[i] = 0; bits[i] = 0; } } void loop() { //Check if card present pin is Low if (digitalRead(MAGSTRIPE_CLS) == LOW){ Serial.println("Error: Card Not Available"); return; } //Device Is Detected Set Read LED ON Serial.println("Card Available"); digitalWrite(READ_LED, HIGH); //Wait for Card Input while(digitalRead(MAGSTRIPE_CLS) != LOW){ } //Wait for reset of card reader while(digitalRead(MAGSTRIPE_CLS) == LOW){ } // Show that the card has finished reading... digitalWrite(READ_LED, LOW); Serial.print("Bits Read: "); Serial.println(num_bits); //Decode Data short ret = decode_magdata(data, DATA_BUFFER_LEN); if(ret < 0){ //Reverse Bits and try again //Happens when swipped the opposite way for (short i = 0; i < num_bits / 2; i++) { bool tmp = bitRead(bits[i / 8], (i % 8)); bool tmp2 = bitRead(bits[(num_bits - i) / 8], ((num_bits - i) % 8)); //Swap bits bitWrite(bits[i / 8], (i % 8), tmp2); bitWrite(bits[(num_bits - i) / 8], ((num_bits - i) % 8), tmp); } //Decode Data ret = decode_magdata(data, DATA_BUFFER_LEN); } // Send the data to the computer... Serial.println(); Serial.print("Return Value: "); Serial.println(ret); // If there was an error reading the card, blink the error LED... if (num_bits <= 0) { digitalWrite(ERROR_LED, HIGH); delay(250); digitalWrite(ERROR_LED, LOW); } else{ Serial.print("Return Data: "); Serial.println(data); } reset_data(); }